package model;

import java.awt.Color;
import java.util.ArrayList;
import java.util.List;
import java.util.Observable;

public class Connected4Game extends Observable {

	/**
	 * Enum sullos stato del gioco (turno di un giocatore, vittoria, pareggio)
	 */
	private State gameState;

	/**
	 * area di gioco
	 */
	private GameBoard board;

	private AbstractPlayer p1, p2;
	private AbstractPlayer nextTurnPlayer;
	
	private int lastPlayedRow;
	private int lastPlayedCol;
	
	private GameThread gameThread;

	public Connected4Game(int rows, int cols, AbstractPlayer p1,
			AbstractPlayer p2, boolean enStatistics) {
		board = new GameBoard(rows, cols);
		this.p1 = p1;
		this.p2 = p2;
		gameState = State.PLAYING;
		nextTurnPlayer = p1;
		lastPlayedCol = -1;
		lastPlayedRow = -1;

		gameThread = new GameThread(p1,p2,this, enStatistics);
		
		callNextPlayerTurn();
	}

	public void start()
	{
		gameThread.start();
	}
	
	public void stop() {
		gameThread.interrupt();
	}
	
	public List<List<Double>> getStatistics()
	{
		List<List<Double>> times = new ArrayList<List<Double>>();
		times.add(gameThread.getTimes1());
		times.add(gameThread.getTimes2());
		return times;
	}
	
	/**
	 * Metodo richiamato quando un giocatore esegue una mossa. Viene aggiornato
	 * lo stato del gioco
	 */
	public void update(int playerMove, AbstractPlayer player) {

		// Applico la mossa, verifico la vittoria,...

		// NOTA: spostare metodo colorToSlotPlayer in utils da condividere con
		// interfaccia grafica
		Slot s = colorToSlotPlayer(player.getColor());

		// metodo per ottenere la riga libera, data una colonna
		int r = board.getFreeRow(playerMove);
		if (r >= 0) // mossa valida
		{
			board.setSlot(s, r, playerMove);
			lastPlayedCol = playerMove;
			lastPlayedRow = r;
			
			// verifico vittoria o pareggio
			gameState = checkGameState(board.getBoard());

			// notifico la fine del turno agli observer della partita
			setChanged();
			notifyObservers();

			// caso di prosecuzione della partita (nessuna vittoria o pareggio)

		} else
			throw new RuntimeException("Inserimento in una colonna piena");

	}

	public AbstractPlayer getNextTurnPlayer() {
		return nextTurnPlayer;
	}

	/**
	 * Verifica lo stato di gioco (partita in corso, vittoria di un
	 * giocatore,pareggio).
	 * 
	 * @return stato della partita
	 */
	public static State checkGameState(Slot[][] matrix) {
		State s = State.PLAYING;
		int rows = matrix.length;
		int cols = matrix[0].length;

		// verifica condizione vittoria con posizionamento orizzontale
		for (int row = 0; row < rows; row++) {
			for (int col = 0; col < cols - 3; col++) {
				Slot curr = matrix[row][col];
				if (!curr.equals(Slot.FREE)
						&& curr.equals(matrix[row][col + 1])
						&& curr.equals(matrix[row][col + 2])
						&& curr.equals(matrix[row][col + 3])) {
					s = getVictoryStateFromSlot(curr);
				}
			}
		}

		// verifica condizione vittoria con posizionamento verticale
		for (int col = 0; col < cols; col++) {
			for (int row = rows - 1; row >= 3; row--) {
				Slot curr = matrix[row][col];
				if (!curr.equals(Slot.FREE)
						&& curr.equals(matrix[row - 1][col])
						&& curr.equals(matrix[row - 2][col])
						&& curr.equals(matrix[row - 3][col]))
					s = getVictoryStateFromSlot(curr);
			}
		}
		// verifica condizione vittoria con posizionamento diagonale
		// (principale)
		for (int row = rows - 1; row >= 3; row--) {
			for (int col = cols - 1; col >= 3; col--) {
				Slot curr = matrix[row][col];
				if (!curr.equals(Slot.FREE)
						&& curr.equals(matrix[row - 1][col - 1])
						&& curr.equals(matrix[row - 2][col - 2])
						&& curr.equals(matrix[row - 3][col - 3]))
					s = getVictoryStateFromSlot(curr);
			}
		}
		// verifica condizione vittoria con posizionamento diagonale
		// (secondaria)
		for (int row = rows - 1; row >= 3; row--) {
			for (int col = 0; col < cols - 3; col++) {
				Slot curr = matrix[row][col];
				if (!curr.equals(Slot.FREE)
						&& curr.equals(matrix[row - 1][col + 1])
						&& curr.equals(matrix[row - 2][col + 2])
						&& curr.equals(matrix[row - 3][col + 3]))
					s = getVictoryStateFromSlot(curr);
			}
		}

		// verifica mancanza di mosse disponibili (pareggio)
		int row = 0;
		int col = 0;
		boolean foundFree = false;
		while (row < rows && !foundFree) {
			while (col < cols && !foundFree) {
				Slot curr = matrix[row][col];
				if (curr.equals(Slot.FREE))
					foundFree = true;
				col++;
			}
			row++;
		}
		if (!foundFree && s.equals(State.PLAYING))
			s = State.DRAW;

		return s;
	}

	/**
	 * Comunica al giocatore di turno che deve giocare. Aggiorna il puntatore al
	 * prossimo giocatore.
	 */
	public void callNextPlayerTurn() {
		if (gameState.equals(State.PLAYING)) {
			AbstractPlayer curr = getOtherPlayer(nextTurnPlayer);
			curr.endTurn();
			nextTurnPlayer.takeTurn(board.getBoard());
			nextTurnPlayer = curr;
		}

	}
	
	public AbstractPlayer getOtherPlayer(AbstractPlayer p)
	{
		if (p.equals(p1))
			return p2;
		else
			return p1;
	}

	public Slot colorToSlotPlayer(Color c) {
		Slot s;
		if (c.equals(Color.RED))
			s = Slot.PLAYER1;
		else
			s = Slot.PLAYER2;
		return s;
	}

	private static State getVictoryStateFromSlot(Slot sl) {
		State st;
		switch (sl) {
		case PLAYER1:
			st = State.VICTORY_1;
			break;

		case PLAYER2:
			st = State.VICTORY_2;
			break;
		default:
			st = State.PLAYING;
			break;
		}
		return st;
	}

	public State getGameState() {
		return gameState;
	}

	public GameBoard getBoard() {
		return board;
	}

	public AbstractPlayer getP1() {
		return p1;
	}

	public AbstractPlayer getP2() {
		return p2;
	}

	public int getLastPlayedRow() {
		return lastPlayedRow;
	}

	public int getLastPlayedCol() {
		return lastPlayedCol;
	}


	
	
}
